package cn.blu10ph.trustshare.core;

import cn.blu10ph.trustshare.bean.node.TrustNode;
import cn.blu10ph.trustshare.bean.node.NodeDistance;
import cn.blu10ph.trustshare.util.ConvertUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import java.util.*;

/**
 * <p> RoutingTable </p >
 *
 * @author cxx
 * @date 2024/4/1 23:16
 */
@Slf4j
@Component
public class RoutingTable {

    protected static TrustNode self;
    protected final Map<String, TrustNode> nodeMap = new HashMap<>();
    protected final Map<String, Long> nodeActiveMap = new HashMap<>();

    public static void setSelfNode(TrustNode self) {
        RoutingTable.self = self;
    }

    public TrustNode getSelfNode() {
        return RoutingTable.self;
    }

    public String getNodeId() {
        return RoutingTable.self.getNodeId();
    }

    public TrustNode getNodeById(String nodeId) {
        return nodeMap.get(nodeId);
    }

    public List<TrustNode> findNodeListById(String nodeId) {
        if(getNodeId().equals(nodeId)){
            return Collections.singletonList(getSelfNode());
        }

        TrustNode node = getNodeById(nodeId);
        if(!ObjectUtils.isEmpty(node)){
            return Collections.singletonList(node);
        }

        // compare distance
        List<NodeDistance> nodeDistanceList = new ArrayList<>();
        nodeMap.values().forEach(item->{
            long distance = distanceTo(nodeId, item.getNodeId());
            nodeDistanceList.add(new NodeDistance(item.getNodeId(), distance));
        });
        nodeDistanceList.sort(Comparator.comparingLong(NodeDistance::getDistance));

        // find top 8 node
        List<TrustNode> nodes = new ArrayList<>();
        int maxNum = Math.min(nodeDistanceList.size(), 8);
        for (int i=0;i<maxNum;i++) {
            NodeDistance nodeDistance = nodeDistanceList.get(i);
            log.debug("distance sel [{}]:{}", nodeDistance.getNodeId(), nodeDistance.getDistance());
            TrustNode nodeItem = nodeMap.get(nodeDistance.getNodeId());
            nodes.add(nodeItem);
        }
        return nodes;
    }

    public List<TrustNode> getAllNodeList() {
        return nodeMap.values().stream().toList();
    }

    public void addNode(TrustNode node) {
        log.debug("addNode:{}", node.getNodeId());
        nodeMap.put(node.getNodeId(), node);
    }

    public void removeNode(String nodeId) {
        log.debug("removeNode:{}", nodeId);
        nodeActiveMap.remove(nodeId);
        nodeMap.remove(nodeId);
    }

    public void setNodeActive(String nodeId) {
        long time = System.currentTimeMillis();
        log.debug("setNodeActive:{}->{}", nodeId, time);
        nodeActiveMap.put(nodeId, time);
    }

    public Long getNodeActive(String nodeId) {
        Long time = nodeActiveMap.get(nodeId);
        log.debug("getNodeActive:{}->{}", nodeId, time);
        return time;
    }

    public static long distanceTo(String nodeId1, String nodeId2) {
        if (nodeId1.length() != nodeId2.length()) {
            throw new IllegalArgumentException("Both nodeIDs must be of the same length.");
        }

        long distance = 0;
        byte[] nodeId1Byte = ConvertUtil.str2Hex(nodeId1);
        byte[] nodeId2Byte = ConvertUtil.str2Hex(nodeId2);
        for (int i = 0; i < nodeId1Byte.length; i++) {
            distance = distance*10+(nodeId1Byte[i] & 0xFF) ^ (nodeId2Byte[i] & 0xFF);
        }
        log.debug("distanceTo({},{}):{}", nodeId1, nodeId2, distance);
        return distance;
    }

}
